from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from getInfoFromExcel import extract_production_data
import threading
import os
import time
import json
import asyncio
from typing import List, Dict, Any
from queue import Queue
from datetime import datetime, timedelta
import shutil
from callBtn_modbus import get_regs_with_client,set_regs_with_client,get_lineID,get_ip,get_name,get_line_name_by_ip,get_all_ips,BTN_IP_MAPPING,ModbusTCPClient,blink_led_with_client

# 添加线程锁来确保产量操作的原子性
production_lock = threading.Lock()

#############################################
# 应用启动与配置模块
#############################################

LINES = ['line1', 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8', 'line9'] 

is_total_client = False

app = FastAPI(title="Wabtec生产线监控系统API", description="提供生产线数据的API接口")

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 创建消息队列
message_queue = asyncio.Queue()

# 创建json保存队列
save_queue = Queue()

MONITORING=False

# 创建一个线程列表来存储所有监控线程
monitor_threads = []

main_event_loop=None

# 启动任务
@app.on_event("startup")
async def startup_event():
    global main_event_loop
    main_event_loop = asyncio.get_event_loop()
    # 创建并启动json文件保存线程
    threading.Thread(target=save_processor, daemon=True).start()
    # 创建并启动消息处理线程
    asyncio.create_task(message_processor())
    # # 启动产量更新线程
    # asyncio.create_task(increment_production())
    try:
        # 获取所有按钮IP地址并为每个IP创建监控线程
        all_ips = get_all_ips()
        for ip in all_ips:
            line_name = get_line_name_by_ip(ip)
            # 全局监控程序监控10号寄存器
            thread = start_monitoring(ip, reg_id=10 if not is_total_client else 13)
            monitor_threads.append(thread)
            print(f"已启动IP {ip} (产线: {line_name})的监控线程")

    except Exception as e:
        print(f"错误: {str(e)}")
        
# 启动任务
@app.on_event("shutdown")
async def shutdown_event():
    stop_monitoring()
    for thread in monitor_threads:
        thread.join(timeout=2)
    print("程序已退出")

#############################################
# 数据存储与管理模块
#############################################

# 确保数据目录存在
def ensure_data_dir():
    os.makedirs("json", exist_ok=True)

# 生成JSON文件名
def generate_json_filename():
    current_date = datetime.now()
    # 获取当前周的开始日期（周一）和结束日期（周日）
    start_of_week = current_date - timedelta(days=current_date.weekday())
    end_of_week = start_of_week + timedelta(days=6)
    # 格式化日期为YYYYMMDD
    start_date_str = start_of_week.strftime("%Y%m%d")
    end_date_str = end_of_week.strftime("%Y%m%d")
    return f"json/production_data_{start_date_str}_{end_date_str}.json"


# 消息处理线程，从队列中读取消息并广播
def save_processor():
    print("[文件写入队列] 启动json文件写入队列")
    while True:
        try:
            message = save_queue.get()
            lineID=message["line_id"]
            data=message["data"]
            file_data=load_production_data()
            file_data[lineID]=data[lineID]
            save_production_data(file_data)
            save_queue.task_done()
        except Exception as e:
            print(f"[文件写入队列] 处理保存请求时出错: {str(e)}")
            time.sleep(1)

# 保存生产数据到JSON文件
def save_production_data(data):
    try:
        ensure_data_dir()
        filename = generate_json_filename()
        
        filtered_data = {}
        for line_id in LINES:
            if line_id in data:
                filtered_data[line_id] = data[line_id]
        
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(filtered_data, f, ensure_ascii=False, indent=4)
        print(f"生产数据已保存到 {filename}")
        return True   
    except Exception as e: 
        print(f"保存生产数据失败: {str(e)}")
        return False

# 从JSON文件加载生产数据
def load_production_data():
    try:
        filename = generate_json_filename()
        if os.path.exists(filename):
            with open(filename, 'r', encoding='utf-8') as f:
                data = json.load(f)
            return data
        return {}
    except Exception as e:
        print(f"加载生产数据失败: {str(e)}")
        return {}

# 计算周实际产量
def calculate_weekly_actual(line_id, production_data):
    if line_id not in production_data or 'daily' not in production_data[line_id]:
        return 0
    return sum(production_data[line_id]['daily'].values())

#############################################
# 数据初始化与更新模块
#############################################

# 从Excel数据初始化产线实际生产数据，日产量均赋0
def initialize_actual_data_from_excel(raw_data):
    try:
        ordered_lines = list(BTN_IP_MAPPING.keys())
        production_data = {}
        # 先按生产线分组原始数据
        line_groups = {}
        for item in raw_data:
            line_name = item["Production Line"]
            if line_name not in line_groups:
                line_groups[line_name] = []
            line_groups[line_name].append(item)
        
        # 按照BTN_IP_MAPPING的顺序处理数据
        for idx, line_name in enumerate(ordered_lines):
            if line_name in line_groups:
                line_id = f"line{idx+1}"
                items = line_groups[line_name]
                
                # 计算周计划总量
                weekly_plan = sum(item["Weekly Plan"] for item in items)
                
                # 获取所有日期
                all_dates = set()
                for item in items:
                    if "Daily Plans" in item:
                        all_dates.update(item["Daily Plans"].keys())
                
                # 构建新的日计划结构：{date: [{project: value}, ...]}
                daily_plans = {}
                daily_actuals = {}
                
                for date in all_dates:
                    daily_plans[date] = []
                    daily_actuals[date] = 0
                    
                    # 为每个项目添加计划
                    for item in items:
                        project = item.get("Project", "")
                        plan_value = 0
                        if "Daily Plans" in item and date in item["Daily Plans"]:
                            plan_value = item["Daily Plans"][date]
                        
                        # 即使计划为0也要记录
                        daily_plans[date].append({project: plan_value})
                
                production_data[line_id] = {
                    'lineName': line_name,
                    'weeklyPlan': weekly_plan,
                    'dailyPlans': daily_plans,
                    'daily': daily_actuals
                }

        # 此处初始化，直接将数据写入JSON文件，不用通过队列完成
        save_production_data(production_data)
        print(f"初始化了 {len(production_data)} 条产线数据")
        return True
    except Exception as e:
        print(f"初始化产线数据失败: {str(e)}")

# 定时增加产量的测试函数
async def increment_production():
    
    await asyncio.sleep(10)
    while True:
        try:
            save_queue.join()
            production_data = load_production_data()
            if not production_data:
                await asyncio.sleep(5)
                continue
               
            for line_id, line_data in production_data.items():
                current_date = datetime.now().strftime("%Y/%m/%d")
                
                # 检查选定日期的计划产量
                daily_plan = 0
                if "dailyPlans" in line_data and current_date in line_data["dailyPlans"]:
                    # 新格式：计算所有项目的总计划
                    for project_plan in line_data["dailyPlans"][current_date]:
                        for project, value in project_plan.items():
                            daily_plan += value
                
                # 如果计划产量为0，则不增加
                if daily_plan == 0:
                    continue
                    
                # 获取当前实际产量
                current_actual = 0
                if current_date in line_data['daily']:
                    current_actual = line_data['daily'][current_date]
                # 如果实际产量已经达到或超过计划产量，则不增加
                if current_actual >= daily_plan:
                    continue
                    
                # 增加日产量
                if current_date in line_data['daily']:
                    line_data['daily'][current_date] += 1
                    line_name = line_data['lineName']
                    print(f"产线 {line_name} 在日期 {current_date} 的产量+1，当前: {line_data['daily'][current_date]}，计划: {daily_plan}")
                    
                    message = {
                        "line_id": line_id,
                        "data": production_data
                    }
                    save_queue.put(message)
                    
                    # 计算周实际产量
                    weekly_actual_qty = calculate_weekly_actual(line_id, production_data)
                    
                    message = {
                        "message": "updateActualQty",
                        "line_id": line_id,
                        "line_name": line_data['lineName'],
                        "dailyActualQty": line_data['daily'][current_date],
                        "weeklyActualQty": weekly_actual_qty
                    }
                    await message_queue.put(json.dumps(message))
            
            await asyncio.sleep(10)  # 每5秒增加一次产量，可以根据需要调整
        except Exception as e:
            print(f"[产量更新] 更新产量时出错: {str(e)}")
            await asyncio.sleep(10)

#############################################
# WebSocket通信模块
#############################################

# WebSocket连接管理器
class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
        print(f"[WebSocket] 新客户端连接，当前连接数: {len(self.active_connections)}")

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
        print(f"[WebSocket] 客户端断开连接，当前连接数: {len(self.active_connections)}")

    async def broadcast(self, message: str):
        print(f"[Broadcast] 发送消息到 {len(self.active_connections)} 个客户端: {message}")
        for connection in self.active_connections:
            try:
                await connection.send_text(message)
            except Exception as e:
                print(f"[Broadcast] 发送消息失败: {str(e)}")

manager = ConnectionManager()

# 消息处理线程，从队列中读取消息并广播
async def message_processor():
    print("[消息处理器] 启动消息处理线程")
    while True:
        try:
            message = await message_queue.get()
            await manager.broadcast(message)
            message_queue.task_done()
        except Exception as e:
            print(f"[消息处理器] 处理消息时出错: {str(e)}")
            await asyncio.sleep(1)

# WebSocket路由
@app.websocket("/ws/updateActualQty")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            # 保持连接活跃
            data = await websocket.receive_text()
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        
def websocket_publish(message):
    if main_event_loop:
        asyncio.run_coroutine_threadsafe(message_queue.put(message), main_event_loop)
    else:
        print("主事件循环未设置，无法发送消息。")

#############################################
# API接口模块
#############################################

@app.post("/v1/getProductionInfo")
async def get_production_info():
    try:
        current_date = datetime.now().strftime("%Y/%m/%d")
        production_lines = []
        
        # 等待数据变更全部完成再读取
        save_queue.join()
        production_data = load_production_data()
        if not production_data:
            raise HTTPException(status_code=404, detail="未找到生产数据，请先上传Excel文件")
        
        for line_id, line_data in production_data.items():
            line_name = line_data["lineName"]
            weekly_plan_qty = line_data["weeklyPlan"]
            weekly_actual_qty = calculate_weekly_actual(line_id, production_data)
            
            # 计算周完成率
            weekly_completion_rate = 0
            if weekly_plan_qty > 0:
                weekly_completion_rate = int((weekly_actual_qty / weekly_plan_qty) * 100)
            weekly_plan = {
                "dataItems": [
                    {"label": "计划数量 Plan Qty.", "value": weekly_plan_qty},
                    {"label": "实际产量 Actual Qty.", "value": weekly_actual_qty}
                ],
                "completionRate": weekly_completion_rate
            }
            
            # 获取每日计划与实际产量数据
            daily_plan_value = 0
            project_names = []
            if "dailyPlans" in line_data and current_date in line_data["dailyPlans"]:
                # 新格式：[{project: value}, ...]
                for project_plan in line_data["dailyPlans"][current_date]:
                    for project, value in project_plan.items():
                        daily_plan_value += value
                        # 今日计划产量大于0时才记录项目名称
                        if value > 0 and project and project not in project_names :
                            project_names.append(project)
            
            daily_actual_value = 0
            if 'daily' in line_data and current_date in line_data['daily']:
                daily_actual_value = line_data['daily'][current_date]
            
            # 计算每日完成率
            daily_completion_rate = 0
            if daily_plan_value > 0:
                daily_completion_rate = int((daily_actual_value / daily_plan_value) * 100)
            
            # 项目名称用 " / " 连接
            project_display = " / ".join(project_names) if project_names else ""
            
            daily_plan = {
                "dataItems": [
                    {"label": "项目 Project", "value": project_display},
                    {"label": "计划数量 Plan Qty.", "value": daily_plan_value},
                    {"label": "实际产量 Actual Qty.", "value": daily_actual_value}
                ],
                "completionRate": daily_completion_rate
            }
            
            production_line = {
                "lineId": line_id,
                "lineName": line_name,
                "weeklyPlan": weekly_plan,
                "dailyPlan": daily_plan
            }
            production_lines.append(production_line)
            
        response = {
            "message": "获取生产信息成功",
            "Production_info": production_lines
        }
        
        return response
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取生产信息失败: {str(e)}")


# 添加更新Excel文件路径的接口 - 改为接收文件上传
@app.post("/v1/updateWeekPlanExcel")
async def update_week_plan_excel(file: UploadFile = File(...)):
    try:
        # 清理 plan 文件夹
        if os.path.exists("plan"):
            for file_name in os.listdir("plan"):
                file_path = os.path.join("plan", file_name)
                if os.path.isfile(file_path):
                    os.remove(file_path)
                    print(f"已删除文件: {file_path}")

        # 清理 json 文件夹
        if os.path.exists("json"):
            for file_name in os.listdir("json"):
                file_path = os.path.join("json", file_name)
                if os.path.isfile(file_path):
                    os.remove(file_path)
                    print(f"已删除文件: {file_path}")
        
        os.makedirs("plan", exist_ok=True)
        
        file_name = file.filename
        # 验证文件类型
        if not file_name.endswith(('.xls', '.xlsx')):
            raise HTTPException(status_code=400, detail="只接受Excel文件(.xls或.xlsx)")
        
        # 构建目标文件路径
        target_path = os.path.join("plan", file_name)
        # 保存上传的文件
        with open(target_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
        print(f"收到excel文件上传，已保存到：{target_path}")
        
        # 从Excel文件获取原始数据
        raw_data = extract_production_data(target_path)
        # 初始化产线实际生产数据并写入JSON，会清理原文件夹
        initialize_actual_data_from_excel(raw_data)
        
        return {
            "message": "Excel文件上传成功",
            "excel_path": target_path,
            "lines_count": len(raw_data)
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"更新Excel文件失败: {str(e)}")



@app.post("/v1/precheck")
async def precheck():
    current_date = datetime.now().strftime("%Y/%m/%d")
    
    filename = generate_json_filename()

    # 检查文件是否存在
    if not os.path.exists(filename):
        return {
            "hasPlan": False,
            "date": current_date,
            "message": f"计划文件不存在，请重新上传"
        }

    try:
        save_queue.join()
        production_data = load_production_data()

        # 基础结构检查
        if not isinstance(production_data, dict):
            return {
                "hasPlan": False,
                "date": current_date,
                "message": "数据格式错误，无法读取生产数据"
            }

        # 日计划检查
        daily_plans_exist = False
        for line_id in LINES:
            daily_plans = production_data.get(line_id, {}).get("dailyPlans", {})
            if current_date in daily_plans:
                daily_plans_exist = True
        if daily_plans_exist:
            return {
                "hasPlan": True,
                "date": current_date,
                "message": "已找到今日计划"
            }

        return {
            "hasPlan": False,
            "date": current_date,
            "message": "未找到今日计划"
        }

    except Exception as e:
        return {
            "hasPlan": False,
            "date": current_date,
            "message": f"发生异常: {str(e)}"
        }


#############################################
# 按钮逻辑
#############################################



def monitor_button_status(ip: str, reg_id: int = 10, interval: float = 0.1, port: int = 5003):
    btn_name = get_line_name_by_ip(ip)
    print(f"开始监控按钮IP {ip} (产线: {btn_name})的状态, 监控寄存器{reg_id}...")

    client = ModbusTCPClient(ip, port)
    
    try:
        while MONITORING:
            try:
                current_date = datetime.now().strftime("%Y/%m/%d")
                if not client.connected:
                    if not client.connect():
                        continue
                    # 初始化寄存器
                    set_regs_with_client(client, {reg_id: 0})
                
                status = get_regs_with_client(client)
                reg_value = status["regs"][reg_id]
                
                # 检查寄存器值
                if reg_value == 1:
                    print(f"检测到产线{btn_name}产量增加需求")
                    
                    # 使用线程锁确保产量操作的原子性
                    with production_lock:
                        save_queue.join()
                        production_data = load_production_data()
                        line_id = get_lineID(btn_name)
                        line_data = production_data[line_id]
                        
                        # 获取当前日期的计划产量
                        daily_plan = 0
                        if "dailyPlans" in line_data and current_date in line_data["dailyPlans"]:
                            # 累加当日所有项目的计划产量
                            daily_plans_list = line_data["dailyPlans"][current_date]
                            for plan_item in daily_plans_list:
                                for project_name, project_plan in plan_item.items():
                                    daily_plan += project_plan


                        # # 获取当前日期的实际产量
                        # current_actual = 0
                        # if current_date in line_data['daily']:
                        #     current_actual = line_data['daily'][current_date]
                        #     # 如果实际产量已经达到或超过计划产量，则不增加
                        #     if current_actual >= daily_plan:
                        #         # 重置寄存器值为0
                        #         set_regs_with_client(ip, {reg_id: 0}, port)
                        #         continue
                            
                        # 增加日产量
                        if current_date in line_data['daily']:

                            line_data['daily'][current_date] += 1
                            line_name = line_data['lineName']
                            print(f"产线 {line_name} 在日期 {current_date} 的产量+1，当前: {line_data['daily'][current_date]}，计划: {daily_plan}")
                            production_data[line_id] = line_data
                            message = {
                                "line_id": line_id,
                                "data": production_data
                            }
                            save_queue.put(message)
                            
                            # 更新周实际产量
                            weekly_actual_qty = calculate_weekly_actual(line_id, production_data)
                            message = {
                                "message": "updateActualQty",
                                "line_id": line_id,
                                "line_name": line_data['lineName'],
                                "dailyActualQty": line_data['daily'][current_date],
                                "weeklyActualQty": weekly_actual_qty
                            }
                            websocket_publish(json.dumps(message))
                        
                    # 重置寄存器值为0
                    set_regs_with_client(client, {reg_id: 0})
                    print(f"已重置寄存器{reg_id}值为0")
                    # 如果是单独产线控制程序，才控制闪烁
                    if not is_total_client:
                        blink_led_with_client(client)

                    
                elif reg_value == 2:
                    # 等待直到第11位寄存器值变为0
                    # print(f"等待第11位寄存器值变为0...")
                    # 一直等待直到释放, 此时灯状态为常亮
                    while True:
                        status = get_regs_with_client(client)
                        isButtonLongPressed = status["regs"][11]
                        if isButtonLongPressed == 0:
                            break
                        time.sleep(interval)

                    print(f"检测到产线{btn_name}产量减少需求")
                    
                    # 使用线程锁确保产量操作的原子性
                    with production_lock:
                        save_queue.join()
                        production_data = load_production_data()
                        line_id = get_lineID(btn_name)
                        line_data = production_data[line_id]
                        
                        # 获取当前日期的计划产量
                        daily_plan = 0
                        if "dailyPlans" in line_data and current_date in line_data["dailyPlans"]:
                            # 累加当日所有项目的计划产量
                            daily_plans_list = line_data["dailyPlans"][current_date]
                            for plan_item in daily_plans_list:
                                for project_name, project_plan in plan_item.items():
                                    daily_plan += project_plan

                        # 获取当前日期的实际产量
                        current_actual = 0
                        if current_date in line_data['daily']:
                            current_actual = line_data['daily'][current_date]
                            # 如果实际产量为0，则不减少
                            if current_actual == 0:
                                # 重置寄存器值为0
                                set_regs_with_client(client, {reg_id: 0})
                                continue
                            
                        # 减少日产量
                        if current_date in line_data['daily']:
                            line_data['daily'][current_date] -= 1
                            line_name = line_data['lineName']
                            print(f"产线 {line_name} 在日期 {current_date} 的产量-1，当前: {line_data['daily'][current_date]}，计划: {daily_plan}")
                            production_data[line_id] = line_data
                            message = {
                                "line_id": line_id,
                                "data": production_data
                            }
                            save_queue.put(message)
                            
                            # 更新周实际产量
                            weekly_actual_qty = calculate_weekly_actual(line_id, production_data)
                            message = {
                                "message": "updateActualQty",
                                "line_id": line_id,
                                "line_name": line_data['lineName'],
                                "dailyActualQty": line_data['daily'][current_date],
                                "weeklyActualQty": weekly_actual_qty
                            }
                            websocket_publish(json.dumps(message))
                
                    # 重置寄存器值为0
                    set_regs_with_client(client, {reg_id: 0})
                    print(f"已重置寄存器{reg_id}值为0")
                    # 如果是单独产线控制程序，才控制闪烁
                    if not is_total_client:
                        blink_led_with_client(client)
                    
                time.sleep(interval)
                
            except Exception as e:
                print(f"监控过程中出错: {str(e)}")
                try:
                    client.disconnect()
                    # 尝试重新连接
                    if not client.connect():
                        print(f"重连失败，等待重试...")
                        time.sleep(interval)
                        continue
                except:
                    print(f"重连过程中出错，等待重试...")
                    time.sleep(interval)
                    continue
    
    except KeyboardInterrupt:
        print("监控被用户中断")
    finally:
        client.disconnect()
        print(f"停止监控按钮IP {ip} (产线: {btn_name})的状态")


def start_monitoring(ip: str, reg_id: int = 10):
    global MONITORING
    MONITORING = True
    monitor_thread = threading.Thread(
        target=monitor_button_status,
        args=(ip, reg_id),
        daemon=True
    )
    monitor_thread.start()
    return monitor_thread

def stop_monitoring():
    """
    停止按钮状态监控
    """
    global MONITORING
    MONITORING = False
    print("已发送停止监控信号")
    
